#!/usr/bin/env perl
use warnings;
use strict;

use FindBin;
use lib ("$FindBin::Bin/../lib", "$FindBin::Bin/../lib/perl5");

use POE (
    'Wheel::Run',
    'Filter::Reference',
    'Component::Server::SOAP',
    'Component::Logger',
);

use TryCatch;
use Getopt::Long;

use YAVMM::GuestManager;
use YAVMM::GuestDB;
use YAVMM::Log::Dispatch::Config;

my $cfg = '';

# couchdb settings
my $couchdb     = '';
my $couchuser   = '';
my $couchpass   = '';

GetOptions(
    'conf|config|c=s'   => \$cfg,
);

## Load up settings object
my $settings = '';

if ($cfg) {
    require YAVMM::Settings::IniFiles
	or die "Can't load YAVMM::Settings::IniFiles: $!\n";

    $settings = YAVMM::Settings::IniFiles->new(file => $cfg);
}
elsif ($couchdb and $couchuser and $couchpass) {
    # connect to local couchdb
}

## Load guest database
my $guestdb = YAVMM::GuestDB->new(settings => $settings);
my $dbtype = $settings->get('cluster', 'guest storage engine');
#$guestdb->load_plugin($dbtype);

### Setup sessions
my $manager = $settings->get('cluster', 'manager');

die "[cluster]|manager has not been set.\n" if not $manager;

my $gm = YAVMM::GuestManager->new(settings => $settings);
#$gm->load_plugin($manager);

## Logger
my $logger_config = YAVMM::Log::Dispatch::Config->new(settings => $settings);

try {
    POE::Component::Logger->spawn(
	ConfigFile => $logger_config,
	Alias      => 'logger'
    );
}
catch (YAVMM::Error $err) {
    die $err->error,"\n";
}

## daemonize

## RPC Handler
POE::Component::Server::SOAP->new(
    ALIAS                   => 'soaprpc',
    ADDRESS                 => '0.0.0.0',
    PORT                    => 1776,
    #SSLKEYCERT              => [ 'private-key.pem', 'public-cert.pem' ],
    #SSLINTERMEDIATECACERT   => 'intermediate-ca-cert.pem',

);

POE::Session->create (
    'inline_states' => {
	'_start'        => \&rpc__start,
	'_stop'         => \&rpc__stop,

	# do stuff
	'guest_start'    => \&rpc_guest_start,    # start a guest
	'guest_stop'     => \&rpc_guest_stop,     # stop a guest
	'guest_kill'     => \&_todo, # kill a guest
	'guest_migrate'  => \&_todo, # migrate a guest
	'test'           => \&rpc_test,
	'sleep'          => \&rpc_sleep,

	'on_task_stdout' => \&rpc_got_task_stdout,
	'on_task_stderr' => \&rpc_got_task_stderr,
	'on_task_signal' => \&rpc_got_task_signal,
	'on_task_close'  => \&rpc_got_task_close,
    },
    args => [ $gm, $settings ],
);

## Stats session
POE::Session->create (
    'inline_states' => {
	'_start'         => \&stats__start,

	'guest_started'  => \&stats_guest_started, # guest has started
	'guest_stopped'  => \&stats_guest_stopped, # guest has stopped
    },
    args => [ $gm, $settings, ],
);

## Guest wrangler session
POE::Session->create (
    'inline_states' => {
	'_start'         => \&wrangler__start,

	'guest_started'  => \&wrangler_guest_started, # guest is successfully running
	'guest_stopped'  => \&wrangler_guest_stopped, # guest has been stopped
    },
    args => [ $gm, $settings ],
);

## Fire!!
POE::Kernel->run;

### Handlers

## RPC handlers

sub rpc__start {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    $heap->{gm}       = $_[ARG0];
    $heap->{settings} = $_[ARG0];

    $kernel->post('logger', 'info', 'Starting yavmmrpc');

    $kernel->alias_set('yavmmrpc');
    $kernel->post( soaprpc => 'ADDMETHOD', yavmmrpc => 'guest_start');
    $kernel->post( soaprpc => 'ADDMETHOD', yavmmrpc => 'guest_stop');
    $kernel->post( soaprpc => 'ADDMETHOD', yavmmrpc => 'test');
    $kernel->post( soaprpc => 'ADDMETHOD', yavmmrpc => 'sleep');
}

sub rpc__stop {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

}

sub rpc_guest_start {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    my $response = $_[ARG0];
    my $params = $response->soapbody();

    my $guest_name = $params->{name};

    if (not defined $guest_name) {
	$response->content([0, 'No name set']);
	$kernel->post('soaprpc', 'DONE', $response);
	return;
    }

    ## check to see if the guest is already running
    my $guest = $heap->{guestdb}->get_guest($guest_name);
    if ($guest->status eq 'running') {
	$response->content([0, 'Guest is already running']);
	$kernel->post('soaprpc', 'DONE', $response);
	return;
    }

    #http://poe.perl.org/?POE_Cookbook/Child_Processes_3
    my $child = POE::Wheel::Run->new(
	CloseOnCall    =>,
	StdoutEvent    => 'on_task_stdout',
	StderrEvent    => 'on_task_stderr',
	CloseEvent     => 'on_task_close',
	Program        => \&_start_guest,
	ProgramArgs    => [$kernel, $heap->{gm}, $guest],
    );

    $kernel->post('logger', 'info', "Guest $guest_name started");
    $response->content([1, "Guest $guest_name started"]);
    $kernel->post('soaprpc', 'DONE', $response);

    $kernel->post('stats_collector', 'guest_started', $guest_name);
    $kernel->post('wrangler',        'guest_started', $guest_name);
}

sub rpc_guest_stop {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    my $response = $_[ARG0];
    my $params = $response->soapbody();

    my $guest_name = $params->{name};

    if (not defined $guest_name) {
	$response->content([0, 'No name set']);
	$kernel->post('soaprpc', 'DONE', $response);
	return;
    }

    ## check to see if the guest is already running
    my $guest = $heap->{guestdb}->get_guest($guest_name);
    if ($guest->status eq 'running') {
    }
    else {
	$response->content([0, 'Guest is not running']);
	$kernel->post('soaprpc', 'DONE', $response);
	return;
    }
}

sub rpc_test {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    my $response = $_[ARG0];
    my $params = $response->soapbody();

    $kernel->post('logger', 'debug', "Got a request at ".scalar localtime());
    use Data::Dumper;
    $kernel->post('logger', 'debug', "*** PARAMS ***\n", Dumper $params);

    my ($user, $pass) = $response->soaprequest->authorization_basic;

    if (not $user or not $pass) {
	$kernel->post('logger', 'debug', "Permission denied when calling ".$response->soapmethod."()");
	$response->www_authenticate("Basic Realm=\"Yavmm\"");
	$response->code(401);
	$kernel->post('soaprpc', 'FAULT', $response,
		      'Client.Authentication',
		      'Bad user name or password');
    }
    else {
	$kernel->post('logger', 'debug', "User: $user; Pass: $pass");
	$response->content($params);
	$kernel->post('soaprpc', 'DONE', $response);
    }
}

sub rpc_sleep {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    my $response = $_[ARG0];
    my $params = $response->soapbody();

    use Data::Dumper;
    $kernel->post('logger', 'debug', "Sleeping: ");
    $kernel->post('logger', 'debug', "*** PARAMS ***");
    $kernel->post('logger', 'debug', Dumper $params);

    my $child = POE::Wheel::Run->new(
	CloseOnCall   => 1,
	Program       => sub{
	    sleep $params->{time};
	    exit;
	},
	#StdoutFilter  => '', #POE::Filter::Reference->new,
	StdoutEvent   => 'on_task_stdout',
	StderrEvent   => 'on_task_stderr',
	CloseEvent    => 'on_task_close',
    );

    my %task = (
	child      => $child,
	guest_name => 'foo',
    );

    $heap->{task_by_wid}->{$child->ID}  = \%task;
    $heap->{task_by_pid}->{$child->PID} = \%task;

    $kernel->sig_child($child->PID, 'on_task_signal');

    $kernel->post('logger', 'debug', "Calling sleep for $params->{time} secs. (PID: ".$child->PID."; ID:".$child->ID.")");

    $response->content([1, "Sleeping for $params->{time} seconds"]);
    $kernel->post('soaprpc', 'DONE', $response);

    $kernel->post('wrangler',        'guest_started', 'foo');
}

# Wheel event, including the wheel's ID.
sub rpc_got_task_stdout {
    my ($stdout_line, $wheel_id) = @_[ARG0, ARG1];
    my $task = $_[HEAP]{children_by_wid}{$wheel_id};
    print "pid ", $task->{child}->PID, " STDOUT: $stdout_line\n";
}

# Wheel event, including the wheel's ID.
sub rpc_got_task_stderr {
    my ($stderr_line, $wheel_id) = @_[ARG0, ARG1];
    my $task = $_[HEAP]{children_by_wid}{$wheel_id};
    print "pid ", $task->{child}->PID, " STDERR: $stderr_line\n";
}


sub rpc_got_task_close {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    my $taskid = $_[ARG0];

    my $task = delete $heap->{task_by_wid}->{$taskid};
    delete $heap->{task_by_pid}->{$task->{child}->PID};

    $kernel->post('logger', 'debug', "Task ".$task->{child}->ID.": closed.");

    $kernel->post('stats_collector', 'guest_stopped', $task->{guest_name});
    $kernel->post('wrangler',        'guest_stopped', $task->{guest_name});
}

sub rpc_got_task_signal {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    $kernel->post('logger', 'debug', "pid $_[ARG1] exited with status $_[ARG2].");

    my $task = delete $heap->{task_by_pid}{$_[ARG1]};

    $kernel->post('stats_collector', 'guest_started', $task->{guest_name});
    $kernel->post('wrangler',        'guest_stopped', $task->{guest_name});

    # May have been reaped by on_child_close().
    return unless defined $task->{child};

    delete $heap->{task_by_wid}{$task->{child}->ID};
}

## stats handlers

sub stats__start {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    $kernel->alias_set( 'stats_collector' );

    $heap->{gm}       = $_[ARG0];
    $heap->{settings} = $_[ARG0]

    ## init server stats dbs (RRDs?)
}

sub stats_guest_started {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    $kernel->post('logger', 'debug', "In stats_guest_started");

    # start alarm to begin collecting stats on this vm.
}

# may be called twice
sub stats_guest_stopped {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    $kernel->post('logger', 'debug', "In stats_guest_stopped");

    # The guest has stopped. Clear all alarms and
    # stop collecting stats for the guest.
}

## watcher handlers

sub wrangler__start {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    $heap->{gm}       = $_[ARG0];
    $heap->{settings} = $_[ARG0];

    $kernel->alias_set( 'wrangler' );

    ## check for running guests

    ## create a guest stats session for each running guest
}


sub wrangler_guest_started {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    $kernel->post('logger', 'debug', "In wrangler_guest_started");

    # start alarm to check on this vm.
}

# may be called twice
sub wrangler_guest_stopped {
    my $kernel = $_[KERNEL];
    my $heap   = $_[HEAP];

    $kernel->post('logger', 'debug', "In wrangler_guest_stopped");

    # The guest has stopped. Clear all alarms and
    # stop watching the guest.
}

### Utility functions

sub _todo {}

sub _start_guest {
    my $kernel = shift;
    my $gm     = shift;
    my $guest  = shift;

    try {
	$gm->start_guest($guest);
    } catch (YAVMM::Error $err) {
	# Log failure
	$kernel->post('logger', 'error', "Unable to start guest: ".$err);
	return;
    }
}
